/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.base;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import org.urchr.web.db.DBSession;
import org.webos.java.base.db.User;
import org.webos.java.base.db.apps.DBApplication;
import org.webos.java.base.db.apps.Keychain;
import org.webos.java.base.db.apps.Setting;
import org.webos.java.base.utils.CryptoUtil;
import org.webos.java.base.workspace.FrameHolder;
import org.webos.java.base.workspace.Splitter;
import org.webos.java.base.workspace.Workspace;
import org.webos.java.base.workspace.WorkspaceItem;
import org.webos.java.root.Log;
import org.webos.java.root.RandomGUID;

/**
 *
 * @author kostya
 */
abstract public class Application {
    
    private static SimpleDateFormat shortDateFormat = new SimpleDateFormat("HH:mm");
    private String instanceID = "";
    private Session session = null;
    private List<Frame> frames = new ArrayList<Frame>();
    private List<Workspace> workspaces = new ArrayList<Workspace>();
    private String code = "";
    private String name = null;

    public Application(Session aSession) {
        instanceID = new RandomGUID().toString();
        session = aSession;
        ApplicationType appInfo =  getClass().getAnnotation(ApplicationType.class);
        if(appInfo!=null){
            setCode(appInfo.code());
            setName(appInfo.name());
        }
    }

    public void initialize(){
        
    }

    public String getCode() {
        return code;
    }

    protected void setCode(String code) {
        this.code = code;
    }

    /**
     * @return the instanceID
     */
    public String getInstanceID() {
        return instanceID;
    }

    protected DBApplication getApplication(DBSession aSession){
        try {
            DBApplication app = (DBApplication) aSession.createQuery("from DBApplication where code=:code").
                    setString("code", getCode()).uniqueResult();
            return app;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    protected JSONObject frameToJSON(Frame aFrame){
        try {
            JSONObject object = new JSONObject();
            object.put("app", getCode());
            object.put("frame", aFrame.getCode());
            object.put("instance", aFrame.getInstanceID());
            object.put("title", aFrame.getTitle());
            object.put("workspace", aFrame.getParentWorkspace().getCode());
            object.put("closeable", aFrame.isCloseable());
            object.put("dynamic", aFrame.isDynamic());
            object.put("status", aFrame.isShowStatus());
            object.put("icon", aFrame.getIcon());
            object.put("statusLine", aFrame.statusLine);
            if(aFrame.getFrameHolder()!=null){
                object.put("frame_holder", aFrame.getFrameHolder().getCode());
                object.put("frame_workspace", aFrame.getParentWorkspace().getCode());
            }
            FrameType ft = aFrame.getClass().getAnnotation(FrameType.class);
            if(ft!=null){
                object.put("jsClass", ft.jsClass());
            }
            return object;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    protected final void frameTitleChanged(Frame aFrame){
        if(!frames.contains(aFrame))
            return;
        if(session.getCoreApplication()!=null){
            session.getCoreApplication().sendMessage("setFrameTitle", frameToJSON(aFrame));
        }
    }

    public final void workspaceTitleChanged(Workspace aWorkspace){
        if(!workspaces.contains(aWorkspace))
            return;
        if(session.getCoreApplication()!=null){
            JSONObject obj = new JSONObject();
            obj.putOpt("workspace", aWorkspace.getCode());
            obj.putOpt("caption", aWorkspace.getCaption());
            session.getCoreApplication().sendMessage("setWorkspaceTitle", obj);
        }
    }

    protected final void removeFrame(Frame aFrame){
        if(!frames.contains(aFrame))
            return;
        if(session.getCoreApplication()!=null){
            session.getCoreApplication().sendMessage("removeFrame", frameToJSON(aFrame));
        }
        aFrame.terminate();
        frames.remove(aFrame);
    }


    public final void showFrame(Frame aFrame){
        if(!frames.contains(aFrame))
            return;
        if(session.getCoreApplication()!=null){
            session.getCoreApplication().sendMessage("showFrame", frameToJSON(aFrame));
        }

    }

//    public final void addFrame(Frame aFrame){
//        addFrame(aFrame, null);
//    }
//
    public final void addFrame(Frame aFrame){
        if(frames.contains(aFrame))
            return;
        frames.add(aFrame);
        if(session.getCoreApplication()!=null){
            JSONObject obj = frameToJSON(aFrame);
            if(aFrame.getData()!=null)
                obj.putOpt("data", aFrame.getData());
            session.getCoreApplication().sendMessage("addFrame", obj);
        }
        
    }

    public final int getFramesCount(){
        return frames.size();
    }

    public final Frame getFrameAt(int i){
        return frames.get(i);
    }

    private final WorkspaceItem jSONToWorkspaceItem(JSONObject aObject, WorkspaceItem parent){
        try {
            if("splitter".equalsIgnoreCase(aObject.optString("type"))){
                Splitter sp = new Splitter(parent, aObject.getString("code"));
                sp.setRatio(aObject.getDouble("ratio"));
                sp.setVertical(aObject.getBoolean("vertical"));
                sp.setLeftItem(jSONToWorkspaceItem(aObject.getJSONObject("left"), sp));
                sp.setRightItem(jSONToWorkspaceItem(aObject.getJSONObject("right"), sp));
                return sp;
            }
            if("holder".equalsIgnoreCase(aObject.optString("type"))){
                FrameHolder fh = new FrameHolder(parent, aObject.getString("code"));
                fh.setLocked(aObject.getBoolean("locked"));
                fh.setActiveFrame(aObject.optString("activeFrame"));
                JSONArray arr = aObject.getJSONArray("frames");
                for (int i = 0; i < arr.length(); i++) {
                    String frame = arr.optString(i);
                    fh.getFrameCodes().add(frame);
                }
                return fh;
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    private final JSONObject workspaceItemToJSON(WorkspaceItem aItem){
        if(aItem==null)
            return null;
        try {
            JSONObject object = new JSONObject();
            object.put("code", aItem.getCode());
            if(aItem instanceof Splitter){
                Splitter splitter = (Splitter) aItem;
                object.put("type", "splitter");
                object.put("vertical", splitter.isVertical());
                object.put("ratio", splitter.getRatio());
                object.put("left", workspaceItemToJSON(splitter.getLeftItem()));
                object.put("right", workspaceItemToJSON(splitter.getRightItem()));
            }
            
            if(aItem instanceof FrameHolder){
                FrameHolder holder = (FrameHolder) aItem;
                object.put("type", "holder");
                object.put("activeFrame", holder.getActiveFrame());
                object.put("locked", holder.isLocked());
                object.put("frames", new JSONArray(holder.getFrameCodes()));
            }
            return object;
        } catch (Throwable t) {
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    public final Workspace jSONToWorkspace(JSONObject aObject){
        try {
            Workspace ws = new Workspace();
            ws.setCaption(aObject.optString("caption"));
            ws.setCode(aObject.optString("code"));
            ws.setSelected(aObject.getBoolean("selected"));
            ws.setCloseable(aObject.getBoolean("closeable"));
            ws.setItem(jSONToWorkspaceItem(aObject.optJSONObject("item"), null));
            ws.setType(aObject.getInt("type"));
            ws.setDirection(aObject.getInt("direction"));
            ws.setCollapsed(aObject.getBoolean("collapsed"));
            ws.setWidth(aObject.getInt("width"));
            ws.setHeight(aObject.getInt("height"));
            return ws;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    protected Workspace cloneWorkspace(Workspace aWorkspace){
        JSONObject object = workspaceToJSON(aWorkspace);
        Workspace ws = jSONToWorkspace(object);
        ws.setApplication(aWorkspace.getApplication());
        return ws;
    }

    public final JSONObject workspaceToJSON(Workspace aWorkspace){
        try {
            Workspace thisWorkspace = aWorkspace;
            if(getSession().getCoreApplication()!=null){
                aWorkspace = getSession().getCoreApplication().getCachedWorkspace(aWorkspace);
            }
            JSONObject object = new JSONObject();
            object.put("caption", thisWorkspace.getCaption());
            object.put("code", aWorkspace.getCode());
            object.put("item", workspaceItemToJSON(aWorkspace.getItem()));
            object.put("selected", aWorkspace.isSelected());
            object.put("closeable", aWorkspace.isCloseable());

            object.put("type", aWorkspace.getType());
            object.put("direction", aWorkspace.getDirection());
            object.put("collapsed", aWorkspace.isCollapsed());
            object.put("width", aWorkspace.getWidth());
            object.put("height", aWorkspace.getHeight());
            if(aWorkspace.getApplication()!=null)
                object.put("app", aWorkspace.getApplication().getCode());
            return object;
        } catch (Throwable t) {
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    protected final void addWorkspace(Workspace aWorkspace){
        if(workspaces.contains(aWorkspace))
            return;
        workspaces.add(aWorkspace);
        
        if(session.getCoreApplication()!=null){
            session.getCoreApplication().registerWorkspace(aWorkspace);
            session.getCoreApplication().sendAddWorkspace(aWorkspace);
        }
    }

    public final int getWorkspacesCount(){
        return workspaces.size();
    }

    public final Workspace getWorkspaceAt(int i){
        return workspaces.get(i);
    }
    
    /**
     * @return the session
     */
    public final Session getSession() {
        return session;
    }

    public void sendMessage(String aMethodName, JSONObject aMessage){
        try {
            JSONObject message = new JSONObject();
            message.put("type", "message");
            message.put("app", code);
            message.put("method", aMethodName);
            message.put("object", aMessage);
            getSession().sendMessage(message);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    public void showWorkspace(Workspace aWorkspace){
        if(getSession().getCoreApplication()==null)
            return;
        try {
            JSONObject object = new JSONObject();
            object.put("workspace", aWorkspace.getCode());

            getSession().getCoreApplication().sendMessage("showWorkspace", object);
            getSession().getCoreApplication().workspaceSelected(object);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    protected void closeWorkspace(Workspace aWorkspace){
        for (int i = 0; i < getFramesCount(); i++) {
            Frame frame = getFrameAt(i);
            if(aWorkspace.equals(frame.getParentWorkspace())){
                removeFrame(frame);
                i--;
            }
        }
        removeWorkspace(aWorkspace);
    }

    @RemoteMessage
    public void stopApplication(JSONObject aData){
        for (int i = 0; i < getSession().getDialogCount(); i++) {
            Dialog dialog = getSession().getDialogAt(i);
            if(dialog.getApplication()==this){
                if(getSession().removeDialog(dialog))
                    i--;
            }
        }
        while(getWorkspacesCount()>0) {
            Workspace ws = getWorkspaceAt(0);
            removeWorkspace(ws);
        }
        while(frames.size()>0) {
            Frame frame = frames.get(0);
            removeFrame(frame);
        }
        getSession().stopApplication(this);
    }

    protected void requestReOpen(){
        //By default, raise first ws
        if(getWorkspacesCount()>0)
            showWorkspace(getWorkspaceAt(0));
    }

    protected void requestCloseWorkspace(Workspace aWorkspace){
        //By default close all frames, workspaces and terminate application
        stopApplication(null);
    }

    public boolean removeWorkspace(Workspace ws) {
        if(!workspaces.contains(ws))
            return false;
        try {
            JSONObject object = new JSONObject();
            object.put("workspace", ws.getCode());
            getSession().getCoreApplication().sendMessage("removeWorkspace", object);
            workspaces.remove(ws);
            return true;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return false;
    }

    public void terminate(){
        //Default - nothing
    }

    public JSONObject getOptionObject(String name){
        try {
            return new JSONObject(getOption(name, "{}"));
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return new JSONObject();
    }
    
    public String getOption(String name, String _default){
        DBSession dbs = new DBSession();
        try {
            User user = getSession().getUser(dbs);
            DBApplication dba = getApplication(dbs);
            Setting s = (Setting) dbs.createQuery("from Setting where " +
                    "option=:name and user=:user and application=:app").
                    setString("name", name.toLowerCase()).
                    setEntity("user", user).setEntity("app", dba).
                    uniqueResult();
            if(s!=null && s.getValue()!=null)
                return s.getValue();
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
        return _default;
    }
    
    public void setOption(String name, String value){
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            User user = getSession().getUser(dbs);
            DBApplication dba = getApplication(dbs);
            Setting s = (Setting) dbs.createQuery("from Setting where " +
                    "option=:name and user=:user and application=:app").
                    setString("name", name.toLowerCase()).
                    setEntity("user", user).setEntity("app", dba).
                    uniqueResult();
            if(s==null){
                s = new Setting();
                s.setApplication(dba);
                s.setOption(name.toLowerCase());
                s.setUser(user);
            }
            s.setValue(value);
            dbs.save(s);
            dbs.commit();
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
    }

    public String getSecret(String name, String _default){
        DBSession dbs = new DBSession();
        try {
            User user = getSession().getUser(dbs);
            DBApplication dba = getApplication(dbs);
            Keychain ch = (Keychain) dbs.createQuery("from Keychain where " +
                    "login=:name and user=:user and application=:app").
                    setString("name", name.toLowerCase()).
                    setEntity("user", user).setEntity("app", dba).
                    uniqueResult();
            if(ch!=null){
                String decoded = CryptoUtil.decode(getSession().getPassword(), ch.getPassword());
                return decoded!=null?decoded:_default;
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
        return _default;
    }

    public void setSecret(String name, String value){
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            User user = getSession().getUser(dbs);
            DBApplication dba = getApplication(dbs);
            Keychain ch = (Keychain) dbs.createQuery("from Keychain where " +
                    "login=:name and user=:user and application=:app").
                    setString("name", name.toLowerCase()).
                    setEntity("user", user).setEntity("app", dba).
                    uniqueResult();
            if(ch==null){
                ch = new Keychain();
                ch.setApplication(dba);
                ch.setUser(user);
                ch.setLogin(name.toLowerCase());
            }
            ch.setPassword(CryptoUtil.encode(getSession().getPassword(), value));
            dbs.save(ch);
            dbs.commit();
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
    }


    public void sendError(int errorCode, String errorText){
        try {
            JSONObject obj = NetworkManager.getErrorObject(errorCode, errorText);
            obj.put("app", getCode());
            getSession().sendMessage(obj);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    public void workspaceStateChanged(Workspace aWorkspace){
        
    }

    public void addNotify(Notify aNotify, Frame aFrame){
        try {
            JSONObject object = new JSONObject();
            object.put("header", aNotify.getHeader());
            object.put("icon", aNotify.getIcon());
            object.put("text", aNotify.getText());
            object.put("timeout", aNotify.getTimeout());
            object.put("app", getCode());
            object.put("forceAutoRemove", aNotify.isForceAutoRemove());
            if(aFrame!=null){
                object.put("instance", aFrame.getInstanceID());
                object.put("workspace", aFrame.getParentWorkspace()!=null?
                    aFrame.getParentWorkspace().getCode():"");
            }
            if(aNotify.getCreateDate()!=null)
                object.put("date", "["+getSession().normalizeDate(aNotify.getCreateDate(), shortDateFormat)+"] ");
            else
                object.put("date", "-");
            if(getSession().getCoreApplication()!=null)
                getSession().getCoreApplication().sendMessage("addNotify", object);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
